/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.oai.metadataRepository.repository.xmlConfiguration;

import cz.mzk.oai.metadataRepository.metadata.MetadataFormat;
import cz.mzk.oai.metadataRepository.transformation.XsltConfig;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.transform.TransformerConfigurationException;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.Text;
import org.dom4j.XPath;
import org.dom4j.tree.DefaultText;

/**
 *
 * @author Martin Rehanek (rehan at mzk.cz)
 */
class CollectionsCommonConfigLoader {

    private XPath commonFormatsXpath = DocumentHelper.createXPath("/CollectionsAndSetsMapping/General/Common/Records");
    private XPath formatNamesXpath = DocumentHelper.createXPath("Format/@name");
    private XPath commonTransformationsXpath = DocumentHelper.createXPath("/CollectionsAndSetsMapping/General/Common/Transformation");
    private XPath fromFormatNameXpath = DocumentHelper.createXPath("From/Format/@name");
    private XPath toFormatNameXpath = DocumentHelper.createXPath("To/Format/@name");
    private XPath xsltFileXpath = DocumentHelper.createXPath("XsltFile");
    private XPath variablesDefXpath = DocumentHelper.createXPath("/CollectionsAndSetsMapping/General/Variables/Variable");
    private XPath varNameXpath = DocumentHelper.createXPath("./@name");
    private XPath variablesXpath = DocumentHelper.createXPath("//var");
    protected final Document configDoc;
    protected final CollectionsCommonConfig commonConfig;
    protected final Map<String, String> variableMap = new HashMap<String, String>();

    CollectionsCommonConfigLoader(Document configDoc) throws DocumentException {
        try {
            this.configDoc = configDoc;
            loadVariables();
            expandVariables();
            commonConfig = loadCommonConfig();
//            System.out.println("common config:");
//            System.out.println(commonConfig.toString());
        } catch (Exception e) {
            throw new DocumentException(e);
        }
    }

    private CollectionsCommonConfig loadCommonConfig() throws TransformerConfigurationException {
        Set<MetadataFormat> formats = loadCommonMetadataFormats();
        Set<XsltConfig> xsltConfigs = loadCommonTransformations();
        return new CollectionsCommonConfig(formats, xsltConfigs);
    }

    private Set<MetadataFormat> loadCommonMetadataFormats() {
        Element recordsEl = (Element) commonFormatsXpath.selectSingleNode(configDoc);
        return loadMetadataFormats(recordsEl);
    }

    protected Set<MetadataFormat> loadMetadataFormats(Element recordsEl) {
        List<Attribute> nameAttributes = formatNamesXpath.selectNodes(recordsEl);
        Set<MetadataFormat> formats = new HashSet<MetadataFormat>();
        for (Attribute nameAttribute : nameAttributes) {
            formats.add(loadMetadataFormat(nameAttribute));
        }
        return formats;
    }

    private MetadataFormat loadMetadataFormat(Attribute nameAttribute) {
        String formatName = nameAttribute.getText();
        return MetadataFormat.parseString(formatName);
    }

    private Set<XsltConfig> loadCommonTransformations() throws TransformerConfigurationException {
        List<Element> transformations = commonTransformationsXpath.selectNodes(configDoc);
        return loadTransformations(transformations);
    }

    protected Set<XsltConfig> loadTransformations(List<Element> transfEls) throws TransformerConfigurationException {
        Set<XsltConfig> result = new HashSet<XsltConfig>(transfEls.size());
        for (Element transfEl : transfEls) {
            XsltConfig xsltConfig = loadTransformation(transfEl);
            result.add(xsltConfig);
        }
        return result;
    }

    private XsltConfig loadTransformation(Element transfEl) throws TransformerConfigurationException {
        MetadataFormat from = loadFromMetadataFormat(transfEl);
        MetadataFormat to = loadToMetadataFormat(transfEl);
        File xlstFile = loadXsltfile(transfEl);
        return new XsltConfig(from, to, xlstFile);
    }

    private MetadataFormat loadFromMetadataFormat(Element transfEl) {
        Attribute nameAttribute = (Attribute) fromFormatNameXpath.selectSingleNode(transfEl);
        return loadMetadataFormat(nameAttribute);
    }

    private MetadataFormat loadToMetadataFormat(Element transfEl) {
        Attribute nameAttribute = (Attribute) toFormatNameXpath.selectSingleNode(transfEl);
        return loadMetadataFormat(nameAttribute);
    }

    private File loadXsltfile(Element transfEl) {
        Element xsltFileEl = (Element) xsltFileXpath.selectSingleNode(transfEl);
        return new File(xsltFileEl.getTextTrim());
    }

    private void loadVariables() throws Exception {
        List<Node> variables = variablesDefXpath.selectNodes(configDoc);
        for (Node variable : variables) {
            Attribute nameAttr = (Attribute) varNameXpath.selectSingleNode(variable);
            if (nameAttr == null) {
                throw new Exception("element Variable without attribute name");
            }
            String name = nameAttr.getValue();
            String value = ((Element) variable).getTextTrim();
            if (value == null) {
                throw new Exception("element Variable without content");
            }
            updateVariable(name, value);
        }
    }

    private void updateVariable(String name, String value) throws Exception {
        String presentValue = variableMap.get(name);
        if (presentValue != null) {
            throw new Exception("variable '" + name + "' duplicated. Found value '" + presentValue + "', trying to assign '" + value + "'");
        }
        variableMap.put(name, value);
    }

    private void expandVariables() throws Exception {
        List<Node> variables = variablesXpath.selectNodes(configDoc);
        for (Node var : variables) {
            expandVariable((Element) var);
        }
    }

    private void expandVariable(Element var) throws Exception {
        String name = var.getTextTrim();
        if (name == null) {
            throw new Exception("Empty variable used in configuration");
        }
        String value = variableMap.get(name);
        if (value == null) {
            throw new Exception("No value found for variable '" + name + "'");
        }
        replaceWithValue(var, value);
    }

    private void replaceWithValue(Element var, String value) {
        Element parent = var.getParent();
        List contentOfParent = parent.content();
        int index = contentOfParent.indexOf(var);
        Text text = new DefaultText(parent, value);
        contentOfParent.set(index, text);
    }
}
